package top.ltc_cn.minecraft_manager.service;

import cn.dev33.satoken.stp.StpUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.RestTemplate;
import top.ltc_cn.minecraft_manager.dto.Oauth2JwksKeyResponse;
import top.ltc_cn.minecraft_manager.dto.Oauth2TokenResponse;
import top.ltc_cn.minecraft_manager.dto.OidcConfiguration;
import top.ltc_cn.minecraft_manager.utils.JwtValidator;
import top.ltc_cn.springboot_tools.common.LtcResult;
import top.ltc_cn.springboot_tools.tools.LtcUtils;
import top.ltc_cn.springboot_tools.tools.satoken.stp.StpMinecraftPlayerUtil;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.Map;

import static top.ltc_cn.springboot_tools.tools.DataToMapUtils.dataToMap;

@Slf4j
@Service
public class PlayerLoginService {

    private final RestTemplate restTemplate;
    private final OAuth2ClientProperties oAuth2ClientProperties;
    private final JwtValidator jwtValidator;

    public PlayerLoginService(RestTemplate restTemplate, OAuth2ClientProperties oAuth2ClientProperties, JwtValidator jwtValidator) {
        this.restTemplate = restTemplate;
        this.oAuth2ClientProperties = oAuth2ClientProperties;
        this.jwtValidator = jwtValidator;
    }

    private static final SecureRandom secureRandom = new SecureRandom();
    private static final Base64.Encoder base64Encoder = Base64.getUrlEncoder();

    // 使用(48字节)长度，符合现代安全标准
    private static final int STATE_BYTE_LENGTH = 64;

    /**
     * 生成256位高安全state，符合OAuth 2.0安全最佳实践
     * 特性：
     * 1. 使用密码学安全的SecureRandom
     * 2. 采用URL安全的Base64编码
     * 3. 足够长度防止暴力破解(2^256种可能)
     */
    public static String generateSecureState() {
        byte[] randomBytes = new byte[STATE_BYTE_LENGTH];
        secureRandom.nextBytes(randomBytes);
        return base64Encoder.encodeToString(randomBytes);
    }

    public static String buildUrlWithParams(String baseUrl, Map<String, String> params) {
        StringBuilder sb = new StringBuilder(baseUrl);
        if (params != null && !params.isEmpty()) {
            sb.append("?");
            for (Map.Entry<String, String> entry : params.entrySet()) {
                if (sb.length() > baseUrl.length()) {
                    sb.append("&");
                }
                sb.append(entry.getKey());
                sb.append("=");
                sb.append(entry.getValue());
            }
        }
        return sb.toString();
    }


    @Cacheable(value = "oidc-config", unless = "#result == null")
    public OidcConfiguration getOidcConfig(String oidcConfigUrl) {
        try {
            ResponseEntity<OidcConfiguration> response = restTemplate.getForEntity(
                    oidcConfigUrl,
                    OidcConfiguration.class);
            if (response.getStatusCode().is2xxSuccessful()) {
                return response.getBody();
            } else {
                throw new RuntimeException("Failed to fetch OIDC config: " + response.getStatusCode());
            }
        } catch (HttpClientErrorException e) {
            throw new RuntimeException("Client error while fetching OIDC config: " + e.getMessage(), e);
        } catch (HttpServerErrorException e) {
            throw new RuntimeException("Server error while fetching OIDC config: " + e.getMessage(), e);
        } catch (Exception e) {
            throw new RuntimeException("Unexpected error while fetching OIDC config", e);
        }
    }

    @CacheEvict(value = "oidc-config", allEntries = true)
    public void evictOidcConfigCache() {
        // 方法体可以为空，注解已经实现了缓存清除功能
    }

    public LtcResult<?> tokenEndpoint(OidcConfiguration oidcConfig, String code) {
        OAuth2ClientProperties.Registration registration = oAuth2ClientProperties.getRegistration().get("default");
        Map<String, String> params = new LinkedHashMap<>();
        params.put("grant_type", "authorization_code");
        params.put("code", code);
        params.put("client_id", registration.getClientId());
        params.put("client_secret", registration.getClientSecret());

        String tokenEndpoint = buildUrlWithParams(oidcConfig.getToken_endpoint(), params);

        try {
            ResponseEntity<Oauth2TokenResponse> response = restTemplate.getForEntity(
                    tokenEndpoint,
                    Oauth2TokenResponse.class
                                                                             );

            Oauth2TokenResponse res = response.getBody();

            if (res == null) {
                throw new RuntimeException("Failed to fetch token endpoint: " + response.getStatusCode());
            }

            if (res.getCode() != 200) {
                return LtcResult.error().setData(res);
            }

            try {
                Map<String, Object> map = jwksEndpoint(oidcConfig, res.getAccess_token());
                Object loginId = map.get("login_id");
                StpUtil.login(loginId);
                return LtcResult.success("登录成功").setData(loginId);
            } catch (Exception e) {
                return LtcResult.error("Invalid access token");
            }
        } catch (Exception e) {
            throw new RuntimeException("Unexpected error while fetching token endpoint", e);
        }
    }

    public Map<String, Object> jwksEndpoint(OidcConfiguration oidcConfig, String accessToken) {
        String jwksEndpoint = buildUrlWithParams(oidcConfig.getJwks_uri(), Map.of("access_token", accessToken));
        try {
            ResponseEntity<Oauth2JwksKeyResponse> response = restTemplate.getForEntity(
                    jwksEndpoint,
                    Oauth2JwksKeyResponse.class
                                                                                    );

            Oauth2JwksKeyResponse res = response.getBody();

            if (res == null) {
                throw new RuntimeException("Failed to fetch jwks endpoint: " + response.getStatusCode());
            }

            return jwtValidator.validateAndGetJwtContent(accessToken, res);
        } catch (Exception e) {
            throw new RuntimeException("Unexpected error while fetching jwks endpoint", e);
        }
    }

}
